using System;
using System.IO;
using System.Collections.Generic;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Security.Cryptography.X509Certificates;

using LumiSoft.Net.IO;
using LumiSoft.Net.SIP.Message;
using LumiSoft.Net.UDP;
using LumiSoft.Net.TCP;

namespace LumiSoft.Net.SIP.Stack
{
    /// <summary>
    /// Implements SIP transport layer. Defined in RFC 3261.
    /// </summary>
    internal class SIP_TransportLayer : IDisposable
    {
        #region class SipTcpPipe

        /// <summary>
        /// Implements SIP TCP pipe. This class just waits incoming data, reads it and processes it.
        /// This is done while socket is closed by remote host or timeout reached.
        /// </summary>
        private class SipTcpPipe
        {
            private bool               m_IsDisposed  = false;
            private SIP_TransportLayer m_pTLayer     = null;
            private TCP_Session        m_pSession    = null;
            private MemoryStream       m_pSipMessage = null;
            private string             m_Transport   = "";

            /// <summary>
            /// Default constructor.
            /// </summary>
            /// <param name="owner">Owner SIP transport layer.</param>
            /// <param name="remoteEP">Remote end point where to connect.</param>
            /// <param name="ssl">Specifies if connects as SSL.</param>
            public SipTcpPipe(SIP_TransportLayer owner,IPEndPoint remoteEP,bool ssl)
            {
                TCP_Client client = new TCP_Client();
                client.Connect(remoteEP,ssl);
                m_pSession = client;

                Start();
            }

            /// <summary>
            /// Default constructor.
            /// </summary>
            /// <param name="owner">Owner SIP transport layer.</param>
            /// <param name="session">Connected session.</param>
            public SipTcpPipe(SIP_TransportLayer owner,TCP_ServerSession session)
            {
                m_pTLayer  = owner;
                m_pSession = session;

                Start();
            }

            #region mehtod Dispose

            /// <summary>
            /// Cleans up any resources being used.
            /// </summary>
            public void Dispose()
            {
                if(m_IsDisposed){
                    return;
                }
                m_IsDisposed = true;

                if(m_pSession != null){
                    m_pSession.Dispose();
                }
                m_pSession = null;

                // Remove from active pipes.
                if(m_pTLayer != null){
                    m_pTLayer.m_pTcpReceiviePipes.Remove(this);
                }
            }

            #endregion


            #region mehtod Start

            /// <summary>
            /// Starts reading SIP request or response.
            /// </summary>
            private void Start()
            {
                // Clear old data.
                m_pSipMessage.SetLength(0);

                // Start reading SIP message header.
                m_pSession.TcpStream.BeginReadHeader(
                    m_pSipMessage,
                    m_pTLayer.m_pSipStack.MaximumMessageSize,
                    SizeExceededAction.ThrowException,
                    new ReadToStreamCallback(this.BeginReadHeader_Completed),
                    null
                );
            }

            #endregion

            #region method SendMessage

            /// <summary>
            /// Sends specifed SIP message to pipe.
            /// </summary>
            /// <param name="message">Raw message what to send.</param>
            public void SendMessage(byte[] message)
            {
                lock(m_pSession){
                    m_pSession.TcpStream.Write(message,0,message.Length);
                }
            }

            #endregion


            #region method BeginReadHeader_Completed

            /// <summary>
            /// This method is called when SIP message header reading has completed.
            /// </summary>
            /// <param name="e">Event data.</param>
            private void BeginReadHeader_Completed(ReadToStream_EventArgs e)
            {
                // Header reading completed sucessfully.
                if(e.Exception ==  null){
                    // Add header terminator blank line.
                    m_pSipMessage.Write(new byte[]{(byte)'\r',(byte)'\n'},0,2);

                    m_pSipMessage.Position = 0;
                    string contentLengthValue = Mime.MimeUtils.ParseHeaderField("Content-Length:",m_pSipMessage);
                    m_pSipMessage.Position = m_pSipMessage.Length;

                    if(contentLengthValue != ""){
                        int contentLength = Convert.ToInt32(contentLengthValue);

                        // Check maximum allowed message size.
                        if(m_pTLayer.m_pSipStack.MaximumMessageSize > 0 && (m_pSipMessage.Length + contentLength) > m_pTLayer.m_pSipStack.MaximumMessageSize){
                            Dispose();
                            return;
                        }

                        // Read content data.
                        m_pSession.TcpStream.BeginRead(m_pSipMessage,contentLength,new ReadToStreamCallback(this.BeginReadData_Completed),null);
                    }
                    // Header only SIP message.
                    else{
                        m_pTLayer.Process(m_Transport,m_pSession.LocalEndPoint,m_pSession.RemoteEndPoint,m_pSipMessage.ToArray());

                        // Wait for new SIP message. 
                        Start();
                    }
                }
                else{
                    Dispose();
                }
            }

            #endregion

            #region method BeginReadData_Completed

            /// <summary>
            /// This method is called when SIP message data reading has completed.
            /// </summary>
            /// <param name="e">Event data.</param>
            private void BeginReadData_Completed(ReadToStream_EventArgs e)
            {
                // Content data readign completed sucessfully.
                if(e.Exception == null){
                    m_pTLayer.Process(m_Transport,m_pSession.LocalEndPoint,m_pSession.RemoteEndPoint,m_pSipMessage.ToArray());

                    // Wait for new SIP message. 
                    Start();
                }
                else{
                    Dispose();
                }
            }

            #endregion


            #region Properties Implementation

            /// <summary>
            /// Gets SIP transport(TCP or TLS) that pipe uses.
            /// </summary>
            public string Transport
            {
                get{ return m_Transport; }
            }

            /// <summary>
            /// Gets when was last read or write activity.
            /// </summary>
            public DateTime LastActivity
            {
                get{ return m_pSession.LastActivity; }
            }

            /// <summary>
            /// Gets pipe local IP end point.
            /// </summary>
            public IPEndPoint LocalEndPoint
            {
                get{ return m_pSession.LocalEndPoint; }
            }

            /// <summary>
            /// Gets pipe remote end point.
            /// </summary>
            public IPEndPoint RemoteEndPoint
            {
                get{ return m_pSession.RemoteEndPoint; }
            }

            #endregion

        }

        #endregion

        private SIP_Stack                     m_pSipStack         = null;
        private bool                          m_IsRunning         = false;
        private IPBindInfo[]                  m_pBinds            = null;
        private UDP_Server                    m_pUdpServer        = null;
        private TCP_Server<TCP_ServerSession> m_pTcpServer        = null;
        private List<SipTcpPipe>              m_pTcpReceiviePipes = null;
        private bool                          m_IsDisposed        = false;
                
        /// <summary>
        /// Default constructor.
        /// </summary>
        /// <param name="sipStack">Reference to SIP stack.</param>
        public SIP_TransportLayer(SIP_Stack sipStack)
        {
            m_pSipStack = sipStack;

            m_pTcpReceiviePipes = new List<SipTcpPipe>();
          
            m_pUdpServer = new UDP_Server();
            m_pUdpServer.ProcessMode = UDP_ProcessMode.Parallel;
            m_pUdpServer.PacketReceived += new PacketReceivedHandler(m_pUdpServer_PacketReceived);
            m_pUdpServer.Error += new ErrorEventHandler(m_pUdpServer_Error);

            m_pTcpServer = new TCP_Server<TCP_ServerSession>();
            m_pTcpServer.SessionCreated += new TCP_Server<TCP_ServerSession>.TCP_ServerSessionHandler(m_pTcpServer_SessionCreated);
                        
            m_pBinds = new IPBindInfo[]{};
        }
                
        #region method Dispose

        /// <summary>
        /// Cleans up any resources being used.
        /// </summary>
        public void Dispose()
        {
            if(m_IsDisposed){
                return;
            }
            m_IsDisposed = true;

            Stop();

            m_IsRunning         = false;
            m_pBinds            = null;
            m_pTcpReceiviePipes = null;

            m_pUdpServer.Dispose();
            m_pUdpServer = null;
        }

        #endregion


        #region method Start

        /// <summary>
        /// Starts listening incoming requests and responses.
        /// </summary>
        public void Start()
        {
            if(m_IsRunning){
                return;
            }
            // Set this flag before running thread, otherwise thead may exist before you set this flag.
            m_IsRunning = true;
                        
            m_pUdpServer.Start();
            m_pTcpServer.Start();            
        }
                                
        #endregion

        #region method Stop

        /// <summary>
        /// Stops listening incoming requests and responses.
        /// </summary>
        public void Stop()
        {
            if(!m_IsRunning){
                return;
            }
            m_IsRunning = false;

            m_pUdpServer.Stop();
            m_pTcpServer.Stop();
                        
            m_pTcpReceiviePipes.Clear();         
        }

        #endregion


        #region method m_pUdpServer_PacketReceived

        /// <summary>
        /// This method is called when new SIP UDP packet has received.
        /// </summary>
        /// <param name="e">Event data.</param>
        private void m_pUdpServer_PacketReceived(UDP_PacketEventArgs e)
        {
            try{
                Process(SIP_Transport.UDP,e.LocalEndPoint,e.RemoteEndPoint,e.Data);
            }
            catch(Exception x){
                m_pSipStack.OnError(x);
            }
        }

        #endregion

        #region method m_pUdpServer_Error

        /// <summary>
        /// This method is called when UDP server unknown error.
        /// </summary>
        /// <param name="sender">Sender.</param>
        /// <param name="e">Event data.</param>
        private void m_pUdpServer_Error(object sender,Error_EventArgs e)
        {
            m_pSipStack.OnError(e.Exception);
        }

        #endregion

        #region method m_pTcpServer_SessionCreated

        /// <summary>
        /// This method is called when SIP stack has got new incoming connection.
        /// </summary>
        /// <param name="e">Event data.</param>
        private void m_pTcpServer_SessionCreated(TCP_ServerSessionEventArgs<TCP_ServerSession> e)
        {
            m_pTcpReceiviePipes.Add(new SipTcpPipe(this,e.Session));
        }

        #endregion
        

        #region method Process

        /// <summary>
        /// Processes SIP data (request or response).
        /// </summary>
        /// <param name="transport">SIP transport what received message.</param>
        /// <param name="localEP">Local IP end point which received this message.</param>
        /// <param name="remoteEP">Remote IP end point what sent this SIP message.</param>
        /// <param name="data">SIP message or may be junk data too.</param>
        private void Process(string transport,IPEndPoint localEP,IPEndPoint remoteEP,byte[] data)
        {
            try{
                // Log
                m_pSipStack.Logger.AddRead(data.Length,"Received (" + data.Length + " bytes): " + localEP.ToString() + " <- " + remoteEP.ToString() + "\r\n" + System.Text.Encoding.UTF8.GetString(data) + "\r\n");
                
                // Bad request or response.
                if(data.Length < 10){
                    return;
                }

                // TODO: Check that advertised transport matches actual received transport.
                              
                // Dedect if sip request or reponse.
                // Response
                if(System.Text.Encoding.UTF8.GetString(data,0,10).ToUpper().StartsWith("SIP")){
                    try{                        
                        SIP_Response response = SIP_Response.Parse(data);
                        response.Validate();

                        ProcessResponse(response);
                    }
                    catch(Exception x){
                        // We have bad response, just skip it.

                        // Log
                        if(m_pSipStack.Logger != null){
                            m_pSipStack.Logger.AddText("Skipping message, parse error: " + x.ToString());
                        }
                    }
                }
                // Request
                else{
                    SIP_Request request = null;
                    try{                    
                        request = SIP_Request.Parse(data);
                        try{
                            request.Validate();
                        }
                        catch(Exception x){
                            // Log
                            if(m_pSipStack.Logger != null){
                                m_pSipStack.Logger.AddText("Invalid request: " + x.Message);
                            }

                            // Bad request, send error to request maker.
                            SendResponse(localEP,request.CreateResponse(SIP_ResponseCodes.x400_Bad_Request + ". " + x.Message));
                            return;
                        }
                    }
                    catch(Exception x){
                        // Log
                        if(m_pSipStack.Logger != null){
                            m_pSipStack.Logger.AddText("Invalid request: " + x.Message);
                        }

                        // We have bad request, try to send bad request error to request maker.
                        SIP_Response badRequestResponse = new SIP_Response();
                        badRequestResponse.StatusCode_ReasonPhrase = SIP_ResponseCodes.x400_Bad_Request + ": " + x.Message;
                        SendResponse(localEP,badRequestResponse);
                        return;
                    }

                    SIP_ValidateRequestEventArgs eArgs = m_pSipStack.OnValidateRequest(request,remoteEP);
                    // Request validated, allow transport layer to handle it.
                    if(eArgs.ResponseCode == null){
                        ProcessRequest(request,localEP,remoteEP);
                    }
                    // Request rejected, return response.
                    else{
                        SendResponse(localEP,request.CreateResponse(eArgs.ResponseCode));
                    }
                }                 
            }
            catch(SocketException s){
                // Skip all socket errors here
                string dummy = s.Message;
            }
            catch(Exception x){
                m_pSipStack.OnError(x);
            }
        }

        #endregion

        #region method ProcessRequest

        /// <summary>
        /// Processes incoming request.
        /// </summary>
        /// <param name="request">SIP request.</param>
        /// <param name="localEP">Local IP end point which accepted request.</param>
        /// <param name="remoteEP">Remote IP end point what created request.</param>
        private void ProcessRequest(SIP_Request request,IPEndPoint localEP,IPEndPoint remoteEP)
        {
            // TODO: process Require: and Session-Expires: header.

            /* RFC 3581 4. Server Behavior.
                When a server compliant to this specification (which can be a proxy
                or UAS) receives a request, it examines the topmost Via header field
                value.  If this Via header field value contains an "rport" parameter
                with no value, it MUST set the value of the parameter to the source
                port of the request.  This is analogous to the way in which a server
                will insert the "received" parameter into the topmost Via header
                field value.  In fact, the server MUST insert a "received" parameter
                containing the source IP address that the request came from, even if
                it is identical to the value of the "sent-by" component.
            */

             /* RFC 3261 18.2.1 Receiving Requests.            
                 Next, the server transport attempts to match the request to a server
                 transaction.  It does so using the matching rules described in
                 Section 17.2.3.  If a matching server transaction is found, the
                 request is passed to that transaction for processing.  If no match is
                 found, the request is passed to the core, which may decide to
                 construct a new server transaction for that request.  Note that when
                 a UAS core sends a 2xx response to INVITE, the server transaction is
                 destroyed.  This means that when the ACK arrives, there will be no
                 matching server transaction, and based on this rule, the ACK is
                 passed to the UAS core, where it is processed.
            */

            // Store request socket.
            request.LocalEndPoint  = localEP;
            request.RemoteEndPoint = remoteEP;

            // RFC 3581 4. Add Received and rport.
            SIP_t_ViaParm via = request.Via.GetTopMostValue();
            via.Received = remoteEP.Address.ToString();
            via.RPort = remoteEP.Port;

            SIP_ServerTransaction transaction = m_pSipStack.TransactionLayer.MatchServerTransaction(request);
            // Pass request to matched server transaction.
            if(transaction != null){
                transaction.ProcessRequest(request);
            }               
            else{
                SIP_Dialog dialog = m_pSipStack.TransactionLayer.MatchDialog(request);
                // Pass request to dialog.
                if(dialog != null){
                    dialog.ProcessRequest(request);
                }
                else{
                    // Pass request to core.
                    m_pSipStack.OnRequestReceived(new SIP_RequestReceivedEventArgs(m_pSipStack,request));
                }
            }            
        }

        #endregion

        #region method ProcessResponse

        /// <summary>
        /// Processes SIP recipient response.
        /// </summary>
        /// <param name="response">SIP response.</param>
        private void ProcessResponse(SIP_Response response)
        {
            /* RFC 3261 18.1.2 Receiving Responses.
                When a response is received, the client transport examines the top
                Via header field value.  If the value of the "sent-by" parameter in
                that header field value does not correspond to a value that the
                client transport is configured to insert into requests, the response
                MUST be silently discarded.
              
                Match resposne to client transaction and pass to it or the response MUST be 
                passed to the core if no matching client transaction.
            */

            //--- Check sent-by. ----------------------------------------------------------------
            // FIX ME: TODO:
            /*
            bool isValidSentBy = false;
            SIP_t_ViaParam via = response.Via.GetTopMostValue();
            if(via.SentBy == m_pSipStack.HostName){
                isValidSentBy = true;
            }
            else{
                // Check all listening points.
                foreach(SipListeningPoint linteningPoint in m_pListeningPoints){
                    if(via.SentBy == linteningPoint.LocalEndPoint.ToString()){
                        isValidSentBy = true;
                        break;
                    }
                }
              
                // For TCP, we must check active connections too if no suitable bind found.
                if(!isValidSentBy){
                    lock(m_pTcpReceiviePipes){
                        foreach(SipTcpPipe pipe in m_pTcpReceiviePipes){
                            if(via.SentBy == pipe.LocalEndPoint.ToString()){
                                isValidSentBy = true;
                                break;
                            }
                        }
                    }
                }
            }
            if(!isValidSentBy){
                m_pSipStack.Logger.AddDebug("Discarding stray response, now waited sent-by value '" + via.SentBy + "' !");
                return;
            }*/
            //-----------------------------------------------------------------------------------

            SIP_ClientTransaction transaction =  m_pSipStack.TransactionLayer.MatchClientTransaction(response);
            // Allow client transaction to process response.
            if(transaction != null){
                transaction.ProcessResponse(response);
            }            
            else{
                // Pass response to dialog.
                SIP_Dialog dialog = m_pSipStack.TransactionLayer.MatchDialog(response);
                if(dialog != null){
                    dialog.ProcessResponse(response);
                }
                // Pass response to core.
                else{                    
                    m_pSipStack.OnResponseReceived(new SIP_ResponseReceivedEventArgs(m_pSipStack,null,response));
                }
            }
        }

        #endregion


        #region method SendRequest
             
        /// <summary>
        /// Sends specified request to the host whats specified in Request-URI. NOTE: Request-URI must
        /// be sip or sips URI, otherwise exception is thrown.
        /// </summary>
        /// <param name="request">SIP request to send.</param>
        /// <exception cref="SIP_TransportException">Is raised when sending fails.</exception>
        public void SendRequest(SIP_Request request)
        {
            /* Target dedection.
                *) Use Route.
                *) Use request-URI.
            */

            SIP_Target target = null;
            if(request.Route.GetAllValues().Length > 0){
                target = new SIP_Target(SIP_Uri.Parse(request.Route.GetTopMostValue().Address.Uri));
            }
            else{
                // Request-URI can be only sip or sips uri, otherwise we don't know where to send request.
                SIP_Uri requestURI = null;
                try{
                    requestURI = SIP_Uri.Parse(request.Uri);                    
                }
                catch{
                    throw new SIP_TransportException("Property request.URI is not SIP uri !");
                }
                target = new SIP_Target(requestURI);
            }

            SendRequest(request,target);
        }

        /// <summary>
        /// Sends request to the specified destination.
        /// </summary>
        /// <param name="request">SIP request to send.</param>
        /// <param name="target">Remote target info.</param>
        /// <exception cref="SIP_TransportException">Is raised when sending fails.</exception>
        public void SendRequest(SIP_Request request,SIP_Target target)
        {
            try{
                SendRequest(request,target.LocalEndPoint,new IPEndPoint(System.Net.Dns.GetHostAddresses(target.Host)[0],target.Port),target.Transport);
            }
            catch(Exception x){
                throw new SIP_TransportException(x.Message);
            }
        }

        /// <summary>
        /// Sends specified request to specified SIP server.
        /// </summary>
        /// <param name="request">SIP request to send.</param>
        /// <param name="localEndPoint">Local end point to use. Value null means first available. NOTE: This is used only if transport = UDP.</param>
        /// <param name="remoteEndPoint">Destination remote endpoint where to send request.</param>
        /// <param name="transport">SIP transport to use. Supported values are defined in SIP_Transport class.</param>
        /// <exception cref="SIP_TransportException">Is raised when sending fails.</exception>
        public void SendRequest(SIP_Request request,SIP_EndPointInfo localEndPoint,IPEndPoint remoteEndPoint,string transport)
        {
            /* RFC 3561 18.1.1 Sending Requests.
                The client side of the transport layer is responsible for sending the
                request and receiving responses.  The user of the transport layer
                passes the client transport the request, an IP address, port,
                transport, and possibly TTL for multicast destinations.
             
                A client that sends a request to a multicast address MUST add the
                "maddr" parameter to its Via header field value containing the
                destination multicast address, and for IPv4, SHOULD add the "ttl"
                parameter with a value of 1.
             
                Before a request is sent, the client transport MUST insert a value of
                the "sent-by" field into the Via header field.  This field contains
                an IP address or host name, and port.  The usage of an FQDN is RECOMMENDED.
              
                For reliable transports, the response is normally sent on the
                connection on which the request was received.  Therefore, the client
                transport MUST be prepared to receive the response on the same
                connection used to send the request.
            
                For unreliable unicast transports, the client transport MUST be
                prepared to receive responses on the source IP address from which the request is sent.
              
                For multicast, the client transport MUST be prepared to receive
                responses on the same multicast group and port to which the request is sent.
            */
               
            try{
                byte[]        data = null;
                SipTcpPipe    pipe = null;
                SIP_t_ViaParm via  = request.Via.GetTopMostValue();

                #region UDP

                // UDP
                if(transport.ToUpper() == SIP_Transport.UDP){
                    IPEndPoint localEP = null;
                    // No local end point specified, find one.
                    if(localEndPoint == null){
                        localEP = m_pUdpServer.GetLocalEndPoint(remoteEndPoint);
                    }
                    else{
                        localEP = localEndPoint.EndPoint;
                    }

                    via.ProtocolTransport = SIP_Transport.UDP;
                    // Set sent-by value.
                    if(!string.IsNullOrEmpty(m_pSipStack.HostName)){
                        via.SentBy = m_pSipStack.HostName;
                    }
                    else{
                        via.SentBy = localEP.ToString();
                    }                                        
                    data = request.ToByteData();
                                                        
                    m_pUdpServer.SendPacket(localEP,data,0,data.Length,remoteEndPoint);

                    // Log
                    m_pSipStack.Logger.AddWrite(data.Length,"Sent (" + data.Length + " bytes): " + localEP.ToString() + " -> " + remoteEndPoint.ToString() + "\r\n<begin>\r\n" + System.Text.Encoding.UTF8.GetString(data) + "<end>\r\n");
                }

                #endregion

                #region TCP/TLS

                // TCP/TLS
                else if(transport.ToUpper() == SIP_Transport.TCP || transport.ToUpper() == SIP_Transport.TLS){
                    if(transport.ToUpper() == SIP_Transport.TCP){
                        via.ProtocolTransport = SIP_Transport.TCP;
                        pipe = GetTcpPipe(SIP_Transport.TCP,remoteEndPoint);
                        // We don't have suitable TCP connection, create new connection.
                        if(pipe == null){
                            pipe = new SipTcpPipe(this,remoteEndPoint,false);
                        }
                    }
                    else if(transport.ToUpper() == SIP_Transport.TLS){
                        via.ProtocolTransport = SIP_Transport.TLS;
                        pipe = GetTcpPipe(SIP_Transport.TLS,remoteEndPoint);
                        // We don't have suitable TCP connection, create new connection.
                        if(pipe == null){
                            pipe = new SipTcpPipe(this,remoteEndPoint,true);
                        }
                    }

                    // Set sent-by value.
                    if(!string.IsNullOrEmpty(m_pSipStack.HostName)){
                        via.SentBy = m_pSipStack.HostName;
                    }
                    // FIX ME: Probably never there is any more Any value.
                    else if(pipe.LocalEndPoint.Address.Equals(IPAddress.Any)){
                        // Get first IP
                        via.SentBy = System.Net.Dns.GetHostAddresses("")[0] + ":" + pipe.LocalEndPoint.Port;
                    }
                    else{
                        via.SentBy = pipe.LocalEndPoint.ToString();
                    }                                                                
                    data = request.ToByteData();

                    // Log
                    m_pSipStack.Logger.AddWrite(data.Length,"Sending (" + data.Length + " bytes): " + pipe.LocalEndPoint.ToString() + " -> " + remoteEndPoint.ToString() + "\r\n<begin>\r\n" + System.Text.Encoding.UTF8.GetString(data) + "<end>\r\n");
           
                    // Send request.
                    pipe.SendMessage(data);
                }

                #endregion
                                    
                else{
                    throw new SIP_TransportException("No transport for protocol '" + transport + "' !");
                }
            }
            catch(Exception x){
                throw new SIP_TransportException(x.Message);
            }
        }

        #endregion

        #region method SendResponse

        /// <summary>
        /// Sends response to request maker.
        /// </summary>
        /// <param name="localEP">Local IP end point to use to send response. This argument has effect for UDP only.</param>
        /// <param name="response">Response to send.</param>
        public void SendResponse(IPEndPoint localEP,SIP_Response response)
        {
            SendResponse(localEP,null,response);
        }

        /// <summary>
        /// Sends response to request maker.
        /// </summary>
        /// <param name="localEP">Local IP end point to use to send response. This argument has effect for UDP only.</param>
        /// <param name="remoteEP">Remote end point where to send response. 
        /// If this value is null, Via header is used to get remote end point.
        /// </param>
        /// <param name="response">Response to send.</param>
        public void SendResponse(IPEndPoint localEP,IPEndPoint remoteEP,SIP_Response response)
        {
            /* RFC 3581 4.  Server Behavior.
                When a server attempts to send a response, it examines the topmost
                Via header field value of that response.  If the "sent-protocol"
                component indicates an unreliable unicast transport protocol, such as
                UDP, and there is no "maddr" parameter, but there is both a
                "received" parameter and an "rport" parameter, the response MUST be
                sent to the IP address listed in the "received" parameter, and the
                port in the "rport" parameter.  The response MUST be sent from the
                same address and port that the corresponding request was received on
                in order to traverse symmetric NATs.

            */

            /* RFC 3261 18.2.2 Sending Responses.
                The server transport uses the value of the top Via header field in
                order to determine where to send a response.  It MUST follow the
                following process:

                  o  If the "sent-protocol" is a reliable transport protocol such as
                     TCP or SCTP, or TLS over those, the response MUST be sent using
                     the existing connection to the source of the original request
                     that created the transaction, if that connection is still open.
                     This requires the server transport to maintain an association
                     between server transactions and transport connections.  If that
                     connection is no longer open, the server SHOULD open a
                     connection to the IP address in the "received" parameter, if
                     present, using the port in the "sent-by" value, or the default
                     port for that transport, if no port is specified.  If that
                     connection attempt fails, the server SHOULD use the procedures
                     in [4] for servers in order to determine the IP address and
                     port to open the connection and send the response to.

                  o  Otherwise, if the Via header field value contains a "maddr"
                     parameter, the response MUST be forwarded to the address listed
                     there, using the port indicated in "sent-by", or port 5060 if
                     none is present.  If the address is a multicast address, the
                     response SHOULD be sent using the TTL indicated in the "ttl"
                     parameter, or with a TTL of 1 if that parameter is not present.

                  o  Otherwise (for unreliable unicast transports), if the top Via
                     has a "received" parameter, the response MUST be sent to the
                     address in the "received" parameter, using the port indicated
                     in the "sent-by" value, or using port 5060 if none is specified
                     explicitly.  If this fails, for example, elicits an ICMP "port
                     unreachable" response, the procedures of Section 5 of [4]
                     SHOULD be used to determine where to send the response.

                  o  Otherwise, if it is not receiver-tagged, the response MUST be
                     sent to the address indicated by the "sent-by" value, using the
                     procedures in Section 5 of [4].
            */
            
            try{                
                SIP_t_ViaParm via = response.Via.GetTopMostValue();

                // End point not specified, get it from Via.
                if(remoteEP == null){                     
                    string host = null;
                    int    port = 5060;

                    // Use received host.
                    if(via.Received != null){
                        host = via.Received;
                    }
                    // Get sent-by host
                    else{
                        host = via.SentBy.Split(':')[0];
                    }
                            
                    // Use rport if received is specified too.
                    if(via.Received != null && via.RPort > 0){
                        port = via.RPort;
                    }
                    // Get port from sent-by
                    else{
                        string[] host_port = via.SentBy.Split(':');
                        if(host_port.Length == 2){
                            port = Convert.ToInt32(host_port[1]);
                        }
                    }
                   
                    remoteEP = new IPEndPoint(System.Net.Dns.GetHostAddresses(host)[0],port);
                }

                byte[] data = response.ToByteData();
                                
                // UDP Multicast
                if(via.ProtocolTransport.ToUpper() == SIP_Transport.UDP && via.Maddr != null){
                    throw new SIP_TransportException("UDP Multicast not implemented !");
                }
                // UDP
                else if(via.ProtocolTransport.ToUpper() == SIP_Transport.UDP){
                    if(m_pUdpServer.Bindings.Length == 0){
                        throw new SIP_TransportException("No UDP transport available, this never should happen !");
                    }
                        
                    // Local end point not specified, so use random.
                    if(localEP == null){
                        m_pUdpServer.SendPacket(data,0,data.Length,remoteEP,out localEP);
                    }
                    // Specifiec local end point requested, probably response to request.
                    else{
                        m_pUdpServer.SendPacket(localEP,data,0,data.Length,remoteEP);
                    }
            
                    // Log
                    m_pSipStack.Logger.AddWrite(data.Length,"Sent (" + data.Length + " bytes): " + localEP.ToString() + " -> " + remoteEP.ToString() + "\r\n<begin>\r\n" + System.Text.Encoding.UTF8.GetString(data) + "<end>\r\n");                        
                }            
                // TCP
                else if(via.ProtocolTransport.ToUpper() == SIP_Transport.TCP){
                    SipTcpPipe pipe = GetTcpPipe(SIP_Transport.TCP,remoteEP);
                    // Not existing connection, create it.
                    if(pipe == null){
                        pipe = new SipTcpPipe(this,remoteEP,true);
                    }
                    pipe.SendMessage(data);

                    // Log
                    m_pSipStack.Logger.AddWrite(data.Length,"Sent (" + data.Length + " bytes): " + pipe.LocalEndPoint.ToString() + " -> " + remoteEP.ToString() + "\r\n<begin>\r\n" + System.Text.Encoding.UTF8.GetString(data) + "<end>\r\n");
                }
                // TCP TLS            
                else if(via.ProtocolTransport.ToUpper() == SIP_Transport.TLS){
                    SipTcpPipe pipe = GetTcpPipe(SIP_Transport.TLS,remoteEP);
                    // Not existing connection, create it.
                    if(pipe == null){
                        pipe = new SipTcpPipe(this,remoteEP,true);
                    }
                    pipe.SendMessage(data);

                    // Log
                    m_pSipStack.Logger.AddWrite(data.Length,"Sent (" + data.Length + " bytes): " + pipe.LocalEndPoint.ToString() + " -> " + remoteEP.ToString() + "\r\n<begin>\r\n" + System.Text.Encoding.UTF8.GetString(data) + "<end>\r\n");
                }
            }
            catch(Exception x){            
                throw new SIP_TransportException(x.Message);
            }
        }

        #endregion


        #region method GetEndPoint

        /// <summary>
        /// Gets suitable host name or IP address for specified address.
        /// </summary>
        /// <param name="address">SIP address.</param>
        /// <returns>Returns host name or IP address.</returns>
        internal string GetEndPoint(SIP_t_NameAddress address)
        {
            if(string.IsNullOrEmpty(m_pBinds[0].HostName)){
                // If any IP, use first available IP.
                if(m_pBinds[0].IP == IPAddress.Any || m_pBinds[0].IP == IPAddress.IPv6Any){
                    foreach(IPAddress ip in System.Net.Dns.GetHostAddresses("")){
                        if(ip == IPAddress.Loopback){
                            if(ip.AddressFamily == m_pBinds[0].IP.AddressFamily){
                                return ip.ToString();
                            }
                        }
                    }
                }
                
                return m_pBinds[0].IP.ToString();
            }
            else{
                return m_pBinds[0].HostName;
            }
        }

        #endregion


        #region method GetTcpPipe

        /// <summary>
        /// Gets specified TCP pipe. Returns null if specified pipe doesn't exist.
        /// </summary>
        /// <param name="protocol">SIP protocol or null if TCP/TLS.</param>
        /// <param name="endPoint">Remote IP end point.</param>
        /// <returns>Returns TCP pipe or null if no match.</returns>
        private SipTcpPipe GetTcpPipe(string protocol,IPEndPoint endPoint)
        {
            // TODO: We need to index endpoint

            lock(m_pTcpReceiviePipes){
                foreach(SipTcpPipe pipe in m_pTcpReceiviePipes){
                    try{
                        if(pipe.RemoteEndPoint.Equals(endPoint)){
                            if(string.IsNullOrEmpty(protocol)){
                                return pipe;
                            }
                            else if(protocol.ToUpper() == pipe.Transport.ToUpper()){
                                return pipe;
                            }
                        }
                    }
                    catch{
                        // Skip errors here, only error case is when pipe is disposing, but we don't care about that.
                    }
                }
            }
            return null;
        }

        #endregion


        #region Properties Implementation

        /// <summary>
        /// Gets if transport layer is running.
        /// </summary>
        public bool IsRunning
        {
            get{ return m_IsRunning; }
        }

        /// <summary>
        /// Gets or sets socket bind info. Use this property to specify on which protocol,IP,port server 
        /// listnes and also if connections is SSL.
        /// </summary>
        public IPBindInfo[] BindInfo
        {
            
            get{ return m_pBinds; }
            
            set{
                if(value == null){
                    throw new ArgumentNullException("BindInfo");
                }

                //--- See binds has changed --------------
                bool changed = false;
                if(m_pBinds.Length != value.Length){
                    changed = true;
                }
                else{
                    for(int i=0;i<m_pBinds.Length;i++){
                        if(!m_pBinds[i].Equals(value[i])){
                            changed = true;
                            break;
                        }
                    }
                }

                if(changed){
                    m_pBinds = value; 
                    
                    // Create listening points.
                    List<IPEndPoint> udpListeningPoints = new List<IPEndPoint>();
                    List<IPBindInfo> tcpListeningPoints = new List<IPBindInfo>();
                    foreach(IPBindInfo bindInfo in m_pBinds){
                        if(bindInfo.Protocol == BindInfoProtocol.UDP){
                            udpListeningPoints.Add(new IPEndPoint(bindInfo.IP,bindInfo.Port));
                        }
                        else{
                            tcpListeningPoints.Add(bindInfo);
                        }
                    }
                    m_pUdpServer.Bindings = udpListeningPoints.ToArray();
                    m_pTcpServer.Bindings = tcpListeningPoints.ToArray();
                }
            }            
        }

        #endregion

    }
}
